import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";
import { promisify } from "util";
import { exec } from "child_process";
import { BackupManager } from "./backup-manager.js";

const execAsync = promisify(exec);

// Add debug logger
function debug(...args: any[]) {
  console.error("[DEBUG]", ...args);
}

const server = new McpServer({
  name: "FileTools",
  version: "1.0.0",
});

// Initialize backup manager
const backupManager = new BackupManager(process.cwd());

server.tool(
  "read_file",
  "Request to read the contents of a file at the specified path. Use this when you need to examine the contents " +
    "of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information " +
    "from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files.",
  { path: z.string() },
  async ({ path: filePath }) => {
    try {
      debug("Reading file:", filePath);
      const content = await fs.readFile(filePath, "utf8");
      debug("File content length:", content.length);
      return {
        content: [{ type: "text", text: content }],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error reading file:", err);
      return {
        content: [{ type: "text", text: `Error reading file: ${err.message}` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "write_to_file",
  "Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content " +
    "after creating a backup. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write " +
    "the file. Creates a backup of existing files before overwriting, maintaining up to 5 recent versions.",
  {
    path: z.string(),
    content: z.string(),
  },
  async ({ path: filePath, content }) => {
    try {
      debug("Writing to file:", filePath);
      debug("Content length:", content.length);

      // Create backup if file exists
      try {
        await fs.access(filePath);
        debug("Creating backup before write");
        await backupManager.createBackup(filePath);
      } catch {
        debug("No existing file to backup");
      }

      // Ensure directory exists
      await fs.mkdir(path.dirname(filePath), { recursive: true });
      await fs.writeFile(filePath, content, "utf8");
      debug("File written successfully");
      return {
        content: [
          { type: "text", text: `File written successfully: ${filePath}` },
        ],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error writing file:", err);
      return {
        content: [{ type: "text", text: `Error writing file: ${err.message}` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "replace_in_file",
  "Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes " +
    "to specific parts of the file. Creates a backup before making any changes. The SEARCH content must match exactly with what's " +
    "in the file, including whitespace and line endings. Multiple SEARCH/REPLACE blocks should be listed in the order they appear " +
    "in the file. Keep blocks concise and focused on the specific changes needed.",
  {
    path: z.string(),
    diff: z.string(),
  },
  async ({ path: filePath, diff }) => {
    try {
      debug("Replacing in file:", filePath);
      const content = await fs.readFile(filePath, "utf8");
      debug("Original content length:", content.length);

      // Create backup before modifications
      debug("Creating backup before replace");
      await backupManager.createBackup(filePath);

      // Parse diff blocks
      const diffBlocks = diff.split("<<<<<<< SEARCH").slice(1);
      debug("Number of diff blocks:", diffBlocks.length);
      let newContent = content;

      for (const block of diffBlocks) {
        const [search, replace] = block.split("=======");
        if (!search || !replace) {
          debug("Invalid diff block format");
          continue;
        }

        const searchContent = search.trim();
        const replaceContent = replace.split(">>>>>>> REPLACE")[0].trim();

        debug("Search content length:", searchContent.length);
        debug("Replace content length:", replaceContent.length);

        newContent = newContent.replace(searchContent, replaceContent);
      }

      debug("New content length:", newContent.length);
      await fs.writeFile(filePath, newContent, "utf8");
      return {
        content: [
          { type: "text", text: `File updated successfully: ${filePath}` },
        ],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error replacing in file:", err);
      return {
        content: [
          { type: "text", text: `Error replacing in file: ${err.message}` },
        ],
        isError: true,
      };
    }
  }
);

server.tool(
  "search_files",
  "Request to perform a regex search across files in a specified directory, providing context-rich results. " +
    "This tool searches for patterns or specific content across multiple files, displaying each match with surrounding context. " +
    "Useful for finding code patterns, todo comments, function definitions, or any text-based information across the project. " +
    "Uses Rust regex syntax for pattern matching.",
  {
    path: z.string(),
    regex: z.string(),
    file_pattern: z.string().optional(),
  },
  async ({ path: searchPath, regex, file_pattern }) => {
    try {
      debug("Searching files:", { path: searchPath, regex, file_pattern });
      const grepCommand = `grep -r -n -P "${regex}" ${searchPath}${
        file_pattern ? ` --include="${file_pattern}"` : ""
      }`;
      debug("Grep command:", grepCommand);
      const { stdout } = await execAsync(grepCommand);
      debug("Search results length:", stdout.length);
      return {
        content: [{ type: "text", text: stdout }],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error searching files:", err);
      return {
        content: [
          {
            type: "text",
            text: `No matches found or error searching files: ${err.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);

server.tool(
  "list_files",
  "Request to list files and directories within the specified directory. If recursive is true, " +
    "it will list all files and directories recursively. If recursive is false or not provided, it will only list " +
    "the top-level contents. Use this to explore directory structures and locate files. More efficient than using " +
    "system commands like 'ls' or 'dir'.",
  {
    path: z.string(),
    recursive: z.boolean().optional(),
  },
  async ({ path: dirPath, recursive = false }) => {
    try {
      debug("Listing files:", { path: dirPath, recursive });
      const files = await listFiles(dirPath, recursive);
      debug("Number of files found:", files.length);
      return {
        content: [{ type: "text", text: files.join("\n") }],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error listing files:", err);
      return {
        content: [
          { type: "text", text: `Error listing files: ${err.message}` },
        ],
        isError: true,
      };
    }
  }
);

server.tool(
  "list_backups",
  "Request to list all available backups for a specific file. Each backup entry includes the timestamp " +
    "of when it was created, allowing you to see the history of changes for the file. This tool is useful before performing " +
    "a rollback to identify the specific version you want to restore. Shows up to 5 most recent backups.",
  {
    path: z.string(),
  },
  async ({ path: filePath }) => {
    try {
      debug("Listing backups for:", filePath);
      const backups = await backupManager.getBackupInfo(filePath);
      const backupList = backups.map(
        (b) =>
          `${path.basename(b.path)} (${new Date(b.timestamp).toLocaleString()})`
      );
      return {
        content: [{ type: "text", text: backupList.join("\n") }],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error listing backups:", err);
      return {
        content: [
          { type: "text", text: `Error listing backups: ${err.message}` },
        ],
        isError: true,
      };
    }
  }
);

server.tool(
  "rollback",
  "Request to restore a file to a previous version from backup. Can specify a particular version by timestamp " +
    "(which you can get from list_backups), or omit the timestamp to restore to the most recent backup. Use this tool when " +
    "you need to undo changes made to a file. The system maintains up to 5 backup versions for each file.",
  {
    path: z.string(),
    timestamp: z.number().optional(),
  },
  async ({ path: filePath, timestamp }) => {
    try {
      debug("Rolling back file:", filePath, "to timestamp:", timestamp);
      await backupManager.rollback(filePath, timestamp);
      return {
        content: [
          {
            type: "text",
            text: `Successfully rolled back ${filePath}${
              timestamp
                ? ` to version from ${new Date(timestamp).toLocaleString()}`
                : " to most recent backup"
            }`,
          },
        ],
      };
    } catch (error) {
      const err = error as Error;
      debug("Error rolling back file:", err);
      return {
        content: [
          { type: "text", text: `Error rolling back file: ${err.message}` },
        ],
        isError: true,
      };
    }
  }
);

// Helper function for recursive file listing
async function listFiles(dir: string, recursive: boolean): Promise<string[]> {
  debug("List files called:", { dir, recursive });
  const entries = await fs.readdir(dir, { withFileTypes: true });
  const files: string[] = [];

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);

    if (entry.isDirectory() && recursive) {
      debug("Processing directory:", fullPath);
      files.push(...(await listFiles(fullPath, recursive)));
    } else {
      debug("Adding file:", fullPath);
      files.push(fullPath);
    }
  }

  return files;
}

// Start the server
const transport = new StdioServerTransport();
debug("Starting MCP server...");
server
  .connect(transport)
  .then(() => {
    debug("MCP server connected successfully");
  })
  .catch((error) => {
    debug("Error connecting MCP server:", error);
    console.error(error);
  });
